package org.sdnstproject.interlayer.interlayerProxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;

import org.projectfloodlight.openflow.exceptions.OFParseError;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFType;
import org.sdnstproject.interlayer.OF13Protocal.FuPortStatus;
import org.sdnstproject.interlayer.openflowfuzzing.Strategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

public class SingleConnection implements Runnable {

	private static final Logger log = LoggerFactory.getLogger(SingleConnection.class);
	private Socket socket = null;
	private String controllerIP;
    private int controllerPort;
    private InputStream in;
    private OutputStream out;
    public SingleConnection other;
    public Strategy strategy;
    
	public LinkedBlockingDeque<ByteBuf> msgQueue;
	
    public volatile boolean running = true;
    public volatile boolean isTocontroller = false;
    public volatile boolean isReady = false;
    
    public SingleConnection(Socket socket, Strategy strategy) {
    	this.socket = socket;
    	this.strategy = strategy;
    }
    public SingleConnection(String controllerIP, int controllerPort, Strategy strategy) {
    	this.controllerIP = controllerIP;
    	this.controllerPort = controllerPort;
    	this.strategy = strategy;
    }
    
    @Override
	public void run() {
		if(isTocontroller) {
			while(socket == null) {
				try {
					socket = new Socket(controllerIP, controllerPort);
					socket.setReuseAddress(true);
					
				} catch (ConnectException e) {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					continue;
				} catch (Exception e) {//IOException SocketException UnknownHostException
					log.error("could not connect to controller!!");
					running = false;
					break;
				}
			}
			try {
				in = socket.getInputStream();
				out = socket.getOutputStream();
			} catch (IOException e2) {
				e2.printStackTrace();
			}
		}else {
			try {
				in = socket.getInputStream();
				out = socket.getOutputStream();
			} catch (IOException e2) {
				e2.printStackTrace();
			}
		}
		
		isReady = true;
		
		while(!other.isReady) {
			if(!other.running) {
				log.error("other occured Exception!!");
				running = false;
				break;
			}
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
        try {
            while (running && !Thread.currentThread().isInterrupted()) {
                ByteBuf newrecv = recvMsg();
                if(newrecv != null) {
//                	this.other.sendRawMsg(newrecv);
                	OFMessage ofMessage =null;
                	if((ofMessage = parseOFMsg(newrecv)) !=null) {
                		String name = isTocontroller?"controller":"switch";
                		log.info("---->from "+name+" recv a "+ofMessage.getType().toString());
                		handleMsgV4(ofMessage, newrecv);
                	}
                	
                }
            } 
            
            
        } catch (Exception e) {
        	running = false;
        	Thread.currentThread().interrupt();
            e.printStackTrace();
        } finally {
        	try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
                if (socket != null)
                    socket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

        }
	}
    
    public void connectToController() throws ConnectException,UnknownHostException,IOException{
    	socket = new Socket(controllerIP, controllerPort);
    	socket.setReuseAddress(true);
    }
    
    public boolean sendRawMsg(ByteBuf buf) {
    	if(!socket.isClosed()) {
	        int length = buf.readableBytes();
	        byte[] bytes = new byte[length];
	        buf.getBytes(buf.readerIndex(), bytes);
	        try {
	            this.out.write(bytes, 0, length);
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	        return true;
    	}
    	log.error(Thread.currentThread().getName() + " is closing, could not send msg");
        this.running = false;
        other.running = false;
        return false;
    }
    
    public ByteBuf recvMsg() throws NumberFormatException, IOException {
    	if(!socket.isClosed()) {
	    	byte[] recv = new byte[8];//8192
	    	int readlen;
	        ByteBuf newrecv = null;
	        if ((readlen = in.read(recv, 0, 8)) != -1) {
	        	if(readlen >= 8) {// && recv[0] >=1 && recv[0] <=6
	        		byte[] lenarr = {recv[2], recv[3]};
	        		int mlen = Integer.parseInt(javax.xml.bind.DatatypeConverter.printHexBinary(lenarr), 16);
	        		newrecv = Unpooled.directBuffer(mlen);
	        		byte[] bytes = new byte[mlen];
	        		newrecv.writeBytes(recv);
	        		newrecv.writerIndex(readlen);
        			if((readlen = in.read(bytes, 0, mlen-8)) != -1){
        				newrecv.writeBytes(bytes);
        			}
	        		return newrecv;
	        	}
	        	log.info("recv too short or version is wrong!!");
	        }
    	 }
 		log.error(Thread.currentThread().getName() + " is closing, could not recv msg");
        this.running = false;
        other.running = false;
        return null;
    }
    
    public OFMessage parseOFMsg(ByteBuf bb) {
    	try {
			return OFFactories.getGenericReader().readFrom(bb);
//			return ofMessage;
		} catch (Exception e) {
			log.info(e.toString()+"<==>"+bb.toString());
			return null;
		}
    	
    }
    
    public boolean handleMsgV4(OFMessage msg, ByteBuf bb) {
    	if(msg.getType() == strategy.ofType && strategy.action.equals("MODIFY")) {
    		try {
        		switch (msg.getType()) {
    				case HELLO:
    					//TODO;
    					break;
    				case ERROR:
    					//TODO;
    					break;
    				case ECHO_REQUEST:
    					//TODO;
    					break;
    				case ECHO_REPLY:
    					//TODO;
    					break;
    				case EXPERIMENTER:
    					//TODO;
    					break;
    				case FEATURES_REQUEST:
    					//TODO;
    					break;
    				case FEATURES_REPLY:
    					//TODO;
    					break;
    				case GET_CONFIG_REQUEST:
    					//TODO;
    					break;
    				case GET_CONFIG_REPLY:
    					//TODO;
    					break;
    				case SET_CONFIG:
    					//TODO;
    					break;
    				case PACKET_IN:
    					//TODO;
    					break;
    				case FLOW_REMOVED:
    					//TODO;
    					break;
    				case PORT_STATUS:
    					FuPortStatus portStatus = new FuPortStatus(bb);
    					portStatus.setField(strategy.field, strategy.actionValue);
    					other.sendRawMsg(portStatus.writeTo());
    					break;
    				case PACKET_OUT:
    					//TODO;
    					break;
    				case FLOW_MOD:
    					//TODO;
    					break;
    				case PORT_MOD:
    					//TODO;
    					break;
    				case STATS_REQUEST:
    					//TODO;
    					break;
    				case STATS_REPLY:
    					//TODO;
    					break;
    				case BARRIER_REQUEST:
    					//TODO;
    					break;
    				case BARRIER_REPLY:
    					//TODO;
    					break;
    				case QUEUE_GET_CONFIG_REQUEST:
    					//TODO;
    					break;
    				case QUEUE_GET_CONFIG_REPLY:
    					//TODO;
    					break;
    				case GROUP_MOD:
    					//TODO;
    					break;
    				case TABLE_MOD:
    					//TODO;
    					break;
    				case ROLE_REPLY:
    					//TODO;
    					break;
    				case ROLE_REQUEST:
    					//TODO;
    					break;
    				case GET_ASYNC_REQUEST:
    					//TODO;
    					break;
    				case GET_ASYNC_REPLY:
    					//TODO;
    					break;
    				case SET_ASYNC:
    					//TODO;
    					break;
    				case METER_MOD:
    					//TODO;
    					break;
    				case ROLE_STATUS:
    					//TODO;
    					break;
    				case TABLE_STATUS:
    					//TODO;
    					break;
    				case REQUESTFORWARD:
    					//TODO;
    					break;
    				case BUNDLE_CONTROL:
    					//TODO;
    					break;
    				case BUNDLE_ADD_MESSAGE:
    					//TODO;
    					break;
    				case CONTROLLER_STATUS:
    					//TODO;
    					break;
    	    	}
    		} catch (NumberFormatException e) {
    			log.info("set field failed, catch NumberFormatException.");
    			other.sendRawMsg(bb);
    			return false;
    		}
    	}else {
    		log.info("not modify-----------");
    		other.sendRawMsg(bb);
    		return false;
		}
    	return true;
    }
    
    public boolean handleMsgV1(OFMessage msg, ByteBuf bb) {
    	if(msg.getType() != strategy.ofType) {
    		return false;
    	}
    	switch (msg.getType()) {
			case HELLO:
				//TODO;
				break;
			case ERROR:
				//TODO;
				break;
			case ECHO_REQUEST:
				//TODO;
				break;
			case ECHO_REPLY:
				//TODO;
				break;
			case EXPERIMENTER:
				//TODO;
				break;
			case FEATURES_REQUEST:
				//TODO;
				break;
			case FEATURES_REPLY:
				//TODO;
				break;
			case GET_CONFIG_REQUEST:
				//TODO;
				break;
			case GET_CONFIG_REPLY:
				//TODO;
				break;
			case SET_CONFIG:
				//TODO;
				break;
			case PACKET_IN:
				//TODO;
				break;
			case FLOW_REMOVED:
				//TODO;
				break;
			case PORT_STATUS:
				//TODO
				break;
			case PACKET_OUT:
				//TODO;
				break;
			case FLOW_MOD:
				//TODO;
				break;
			case PORT_MOD:
				//TODO;
				break;
			case STATS_REQUEST:
				//TODO;
				break;
			case STATS_REPLY:
				//TODO;
				break;
			case BARRIER_REQUEST:
				//TODO;
				break;
			case BARRIER_REPLY:
				//TODO;
				break;
			case QUEUE_GET_CONFIG_REQUEST:
				//TODO;
				break;
			case QUEUE_GET_CONFIG_REPLY:
				//TODO;
				break;
			case GROUP_MOD:
				//TODO;
				break;
			case TABLE_MOD:
				//TODO;
				break;
			case ROLE_REPLY:
				//TODO;
				break;
			case ROLE_REQUEST:
				//TODO;
				break;
			case GET_ASYNC_REQUEST:
				//TODO;
				break;
			case GET_ASYNC_REPLY:
				//TODO;
				break;
			case SET_ASYNC:
				//TODO;
				break;
			case METER_MOD:
				//TODO;
				break;
			case ROLE_STATUS:
				//TODO;
				break;
			case TABLE_STATUS:
				//TODO;
				break;
			case REQUESTFORWARD:
				//TODO;
				break;
			case BUNDLE_CONTROL:
				//TODO;
				break;
			case BUNDLE_ADD_MESSAGE:
				//TODO;
				break;
			case CONTROLLER_STATUS:
				//TODO;
				break;
    	}
    	return true;
    }
	
}
